package org.ala.tiktools.rpc.kcp.netty.server.handler;

import io.netty.channel.Channel;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import org.ala.tiktools.rpc.kcp.config.RpcKcpConfigInfo;
import org.ala.tiktools.rpc.kcp.netty.NettyKcp;
import org.ala.tiktools.rpc.kcp.netty.server.KcpServerChannel;
import org.ala.tiktools.rpc.kcp.netty.server.KcpServerChildChannel;
import org.ala.tiktools.tools.TimestampTools;

import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * ServerChannel负责的Closer
 *
 * @author ala
 * @date 2025-04-15 16:59
 */
public class KcpServerChannelCloser {
    private static final InternalLogger log = InternalLoggerFactory.getInstance(KcpServerChannelCloser.class);
    /**
     *  等待关闭的子channel
     */
    protected Map<InetSocketAddress, CloseWaitTask> closeWaitMap = new HashMap<>();
    /**
     *  关闭标记（true表示关闭任务已经放进队列了，不要重复放置）
     */
    protected boolean scheduleCloseWait = false;
    /**
     *  关闭runner
     */
    protected CloseWaitRunner closeWaitRunner;
    /**
     *
     */
    protected KcpServerChannel serverChannel;
    public KcpServerChannelCloser(KcpServerChannel serverChannel) {
        this.serverChannel = serverChannel;
        closeWaitRunner = new CloseWaitRunner();
    }


    /**
     *  取待关闭的任务
     */
    public CloseWaitTask getCloseWaitTask(InetSocketAddress addr) {
        return closeWaitMap.get(addr);
    }
    /**
     *  删除待关闭任务
     */
    public void removeCloseWaitTask(InetSocketAddress addr) {
        closeWaitMap.remove(addr);
    }


    /**
     *  关闭 closeWaitChildChannels 里的子channel
     */
    public void handleCloseChildChannelList(List<KcpServerChildChannel> closeChildChannelList) {
        closeChildChannelList.forEach(c -> c.unsafe().close(c.unsafe().voidPromise()));
        closeChildChannelList.clear();
    }


    /**
     * 关闭
     * <p>  关闭ServerChannel下记录的所有KcoClientChannel
     * <p>  清理每个KcoClientChannel绑定的资源
     */
    public void doClose() throws Exception {
        Exception ex = null;
        //  关闭本accept
        try {
            serverChannel.javaChannel().close();
        } catch (Exception e) {
            ex = e;
        }
        //  清理所有连接上来的KcpClient
        for (KcpServerChildChannel child : serverChannel.getChildChannelMap().values()) {
            try {
                Channel.Unsafe unsafe = child.unsafe();
                unsafe.close(unsafe.voidPromise());
            } catch (Exception e) {
                log.error("关闭channel失败, channel:{}", child, e);
            }
        }
        if (ex != null) {
            throw ex;
        }
    }


    /**
     *  关闭childChannel
     */
    public void doCloseChildChannel(KcpServerChildChannel childChannel) {
        KcpServerChildChannel cc = serverChannel.getChildChannelMap().remove(childChannel.getRemoteAddress());
        if (cc == null) {
            log.error("找不到待关闭的子channel:{}", childChannel.getRemoteAddress());
            return;
        }
        if (cc != childChannel) {
            log.error("待关闭的子channel已经发生改变:{}", childChannel.getRemoteAddress());
            return;
        }

        NettyKcp nkcp = cc.getNettyKcp();
        //  如果当前kcp是活跃的，并且还有数据没发送
        if (serverChannel.isActive() && nkcp.getState() != -1 && nkcp.checkFlush()) {
            nkcp.setClosed(false);
            int current = TimestampTools.milliSeconds32();
            //  放入待关闭队列，5s后尝试关闭
            int delay = RpcKcpConfigInfo.INSTANCE.getCloseWaitTime();
            closeWaitMap.put(cc.getRemoteAddress(), new CloseWaitTask(childChannel, current + delay));
            tryScheduleCloseWait();
            if (!serverChannel.getWriter().isScheduleUpdate()) {
                serverChannel.getWriter().scheduleUpdate(nkcp.check(current), current);
            }
        }
        //  否则强关
        else {
            nkcp.setClosed(true);
        }
    }
    /**
     *  尝试触发关闭任务
     */
    private void tryScheduleCloseWait() {
        if (closeWaitMap.isEmpty() || scheduleCloseWait) {
            return;
        }
        int delay = RpcKcpConfigInfo.INSTANCE.getCloseWaitTime();
        serverChannel.eventLoop().schedule(closeWaitRunner, delay / 2, TimeUnit.MILLISECONDS);
        scheduleCloseWait = true;
    }
    /**
     *  关闭任务
     */
    public class CloseWaitRunner implements Runnable {
        @Override
        public void run() {
            try {
                int current = TimestampTools.milliSeconds32();
                List<SocketAddress> removes = new ArrayList<>();
                for (Map.Entry<InetSocketAddress, CloseWaitTask> e : closeWaitMap.entrySet()) {
                    CloseWaitTask task = e.getValue();
                    removes.add(e.getKey());
                    if (task == null) { continue; }
                    NettyKcp nkcp = task.nkcp;
                    if (current >= task.closeTs || !nkcp.checkFlush()) {
                        nkcp.setKcpClosed();
                        log.info("[KcpServerChannel CloseWaitRunner] 关闭连接:{}", nkcp);
                    }
                }
                for (SocketAddress remove : removes) {
                    closeWaitMap.remove(remove);
                }
            } finally {
                scheduleCloseWait = false;
                tryScheduleCloseWait();
            }
        }
    }
    /**
     *  延迟关闭任务
     */
    public static class CloseWaitTask {
        KcpServerChildChannel childChannel;
        NettyKcp nkcp;
        int closeTs;
        public CloseWaitTask(KcpServerChildChannel childChannel, int closeTs) {
            this.childChannel = childChannel;
            this.nkcp = childChannel.getNettyKcp();
            this.closeTs = closeTs;
        }

        public KcpServerChildChannel getChildChannel() {
            return childChannel;
        }

        public void setChildChannel(KcpServerChildChannel childChannel) {
            this.childChannel = childChannel;
        }

        public NettyKcp getNkcp() {
            return childChannel.getNettyKcp();
        }

        public void setNkcp(NettyKcp nkcp) {
            this.nkcp = nkcp;
        }

        public int getCloseTs() {
            return closeTs;
        }

        public void setCloseTs(int closeTs) {
            this.closeTs = closeTs;
        }
    }

    public Map<InetSocketAddress, CloseWaitTask> getCloseWaitMap() {
        return closeWaitMap;
    }

    public void setCloseWaitMap(Map<InetSocketAddress, CloseWaitTask> closeWaitMap) {
        this.closeWaitMap = closeWaitMap;
    }
}
